﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SolutionHelper.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the SolutionHelper type.
//   This is largely based on code contined in WSPBuilder.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.SharePoint.Wss.Solutions
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Threading;

    using Collaboris.Utils;

    using Microsoft.SharePoint.Administration;

    public class SolutionHelper : WssHelperBase
    {
        #region Properties (1)

        /// <summary>
        /// used to tell sharepoint timer service when to perform operations requested of it
        /// </summary>
        private static DateTime Immediately
        {
            get { return DateTime.Now - TimeSpan.FromDays(1); }
        }

        #endregion Properties

        #region Methods (5)

        // Public Methods (2) 

        /// <summary>
        /// Finds a solution in the SharePoint farm
        /// </summary>
        /// <param name="solutionID">the SolutionID (GUID) of the solution to </param>
        /// <returns>returns the soltion if found, and null if not found</returns>
        public static SPSolution LocateInFarm(Guid solutionID)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "LocateInFarm");

            TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Attempting to Locate this Solution : '" + solutionID + "'");
            string id = solutionID.ToString();

            // move through all of the solutions we can find and see if 
            // we can locate the one we are looking for
            foreach (SPSolution sol in SPFarm.Local.Solutions)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Found this solution : '" + sol.Name + "'");
                if (string.Equals(sol.SolutionId.ToString(), id, StringComparison.InvariantCultureIgnoreCase))
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution was Located in the Farm");
                    TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "LocateInFarm");
                    return sol;
                }
            }

            TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution not located in the Farm");
            TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "LocateInFarm");
            return null;
        }

        /// <summary>
        /// Stops the solution jobs.
        /// </summary>
        /// <param name="solution">The solution.</param>
        public static void StopSolutionJobs(SPSolution solution)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "StopSolutionJobs");
            TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Stopping Jobs for solution : '" + solution.Name + "'");
            ValidationHelper.VerifyObjectArgument(solution, "solution");

            try
            {
                if (solution.JobExists)
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution Job Exists");

                    // is the job already running
                    if (solution.JobStatus == SPRunningJobStatus.Initialized)
                    {
                        throw new Exception("A deployment job already running for this solution.");
                    }

                    // find the running job
                    SPJobDefinition definition = null;
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Attempting to bind to a matching SPJobDefinition");

                    foreach (SPJobDefinition jobdefs in SPFarm.Local.TimerService.JobDefinitions)
                    {
                        if ((jobdefs.Title != null) && jobdefs.Title.Contains(solution.Name))
                        {
                            TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "This job was found : '" + jobdefs.Title + "'");
                            definition = jobdefs;
                            break;
                        }
                    }

                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Definition not found");

                    if (definition != null)
                    {
                        TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "A timer job was found so deleting it");
                        definition.Delete(); // kill if it was found

                        TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Sleeping");
                        Thread.Sleep(1000); // give it time to delete
                    }
                }

                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "StopSolutionJobs");
            }
            catch (Exception ex)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "StopSolutionJobs", ex);
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "StopSolutionJobs");
                throw;
            }
        }


        // Private Methods (3) 

        /// <summary>
        /// gets all of the currently configured applications for a solution
        /// </summary>
        /// <param name="solution">solution get get configured applications for</param>
        /// <param name="applications">the collection to add it to</param>
        private static void AddAllConfiguredWebApplications(SPSolution solution, ICollection<SPWebApplication> applications)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "AddAllConfiguredWebApplications");
            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "SolutionHelper",
                    "Adding All Configured Web Applications ('SPSolution.DeployedWebApplications')");

            foreach (SPWebApplication app in solution.DeployedWebApplications)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Adding '" + app.Name + "'");
                applications.Add(app);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "AddAllConfiguredWebApplications");
        }

        /// <summary>
        ///  gets all of the currently configured applications on the current sharepoint content server
        /// </summary>
        /// <param name="applications">the collection to add it to</param>
        private static void AddAllContentWebApplications(ICollection<SPWebApplication> applications)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "AddAllContentWebApplications");
            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "SolutionHelper",
                    "Adding All Content Web Applications ('SPWebService.ContentService.WebApplications')");

            foreach (SPWebApplication app2 in SPWebService.ContentService.WebApplications)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Adding '" + app2.Name + "'");
                applications.Add(app2);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "AddAllContentWebApplications");
        }

        private static void AddContentWebApplication(ICollection<SPWebApplication> applications, string webAppName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "AddContentWebApplication");
            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "SolutionHelper",
                    "Adding All Content Web Applications ('SPWebService.ContentService.WebApplications')");

            foreach (SPWebApplication app2 in SPWebService.ContentService.WebApplications)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Checking '" + app2.Name + "'");
                if (app2.Name.ToLower() == webAppName.ToLower())
                    applications.Add(app2);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "AddContentWebApplication");
        }

        /// <summary>
        /// gets all of the currently configured applications on the current sharepoint server
        /// </summary>
        /// <param name="applications">the collection to add it to</param>
        private static void AddAllWebApplications(ICollection<SPWebApplication> applications)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "AddAllWebApplications");
            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "SolutionHelper",
                    "Adding All Web Applications ('SPWebService.AdministrationService.WebApplications')");

            foreach (SPWebApplication app1 in SPWebService.AdministrationService.WebApplications)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Adding '" + app1.Name + "'");
                applications.Add(app1);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "AddAllWebApplications");
        }

        /// <summary>
        /// gets all of the currently configured applications on the current sharepoint server
        /// </summary>
        /// <param name="applications">
        /// the collection to add it to
        /// </param>
        /// <param name="appName">
        /// The app Name.
        /// </param>
        private static void AddWebApplication(ICollection<SPWebApplication> applications, string appName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "AddWebApplication");
            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "SolutionHelper",
                    "Adding All Web Applications ('SPWebService.AdministrationService.WebApplications')");

            foreach (SPWebApplication app1 in SPWebService.AdministrationService.WebApplications)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Checking '" + app1.Name + "'");
                if (app1.Name.ToLower() == appName.ToLower())
                    applications.Add(app1);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "AddWebApplication");
        }

        #endregion Methods

        #region IsAlreadyInstalled

        /// <summary>
        /// returns true if the solution is already installed in the sharepoint server
        /// </summary>
        /// <param name="solutionID">the SolutionId (GUID) of the solution</param>
        /// <returns>true if installed</returns>
        public static bool IsAlreadyInstalledByID(string solutionID)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "IsAlreadyInstalledByID");
            TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "IsAlreadyInstalledByID");
            return IsAlreadyInstalled(new Guid(solutionID));
        }

        /// <summary>
        /// returns true if the solution is already installed in the sharepoint server
        /// </summary>
        /// <param name="solutionID">the SolutionId (GUID) of the solution</param>
        /// <returns>true if installed</returns>
        public static bool IsAlreadyInstalled(Guid solutionID)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "IsAlreadyInstalled");
            TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Testing to see if solutions is already installed ('" + solutionID + "')");
            try
            {
                SPSolution sln = LocateInFarm(solutionID);
                if (sln != null)
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution found");
                    return true;
                }

                TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution not found");
                return false;
            }
            catch (NullReferenceException nref)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "IsAlreadyInstalled", nref);
                return false;
            }
            catch (Exception ee)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "IsAlreadyInstalled", ee);
                throw new Exception(ee.Message, ee);
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "IsAlreadyInstalled");
            }
        }

        #endregion IsAlreadyInstalled

        #region Install solution

        /// <summary>
        /// Installs the solution.
        /// </summary>
        /// <param name="solutionFile">The solution file.</param>
        /// <param name="solutionID">The solution ID.</param>
        /// <returns></returns>
        public static bool InstallSolution(string solutionFile, string solutionID, bool deploy)
        {
            return InstallSolution(solutionFile, new Guid(solutionID), deploy);
        }


        /// <summary>
        /// Installs the solution.
        /// </summary>
        /// <param name="solutionFilePath">The solution file path.</param>
        /// <param name="solutionID">The solution ID.</param>
        /// <returns></returns>
        public static bool InstallSolution(string solutionFilePath, Guid solutionID, bool deploy)
        {
            try
            {
                TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "InstallSolution");
                TraceHelper.TraceVerbose(
                        TraceSwitch,
                        "SolutionHelper",
                        "Installing the solution (FilePath='" + solutionFilePath + "' | solutionID='" + solutionID + "')");

                SPSolution solution = LocateInFarm(solutionID);
                if (solution != null)
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution was already found - Stopping Jobs");
                    StopSolutionJobs(solution);

                    // force add
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Uninstalling the existing Solution");
                    UninstallSolution(solutionFilePath, solutionID);

                    solution = null;
                }

                // does not exist so add it
                if (solution == null)
                {
                    TraceHelper.TraceVerbose(
                            TraceSwitch,
                            "SolutionHelper",
                            "Attempting to add '" + solutionFilePath + "' to the Farm solutions store");

                    solution = SPFarm.Local.Solutions.Add(solutionFilePath);
                }

                if (deploy)
                {
                    // check to see if this has web application stuff
                    if (solution.ContainsWebApplicationResource)
                    {
                        TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution contains Web Application Resources");
                        // Add this back in at some pont.
                        ////////////Collection<SPWebApplication> webApplications = new Collection<SPWebApplication>();

                        ////////////// add request web applications
                        ////////////if (deployToAllWebApplications) AddAllWebApplications(webApplications);
                        ////////////if (deployToAllContentWebApplications) AddAllContentWebApplications(webApplications);

                        ////////////if (webApplications.Count == 0) // try to make sure we have at least one
                        ////////////{
                        ////////////    SPWebApplication app = SPWebService.AdministrationService.WebApplications.GetEnumerator().Current;
                        ////////////    if (app == null) app = SPWebService.ContentService.WebApplications.GetEnumerator().Current;
                        ////////////}

                        ////////////// deploy it
                        ////////////// Log.Verbose("WSP package deploying to " + webApplications.Count + " webapplication(s).");
                        ////////////solution.Deploy(Installer.Immediately, true, webApplications, true);
                    }
                    else
                    {
                        // deploy it without web app stuff
                        TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Deploying solution ('" + solution.Name + "') Globally");
                        solution.Deploy(Immediately, true, true);
                    }
                }

                // Wait for the deployment to finish                
                WaitForJobToFinish(solution);
            }
            catch (NullReferenceException exception)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "InstallSolution", exception);
                return false;
            }
            catch (Exception ee)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "InstallSolution", ee);
                throw new Exception(
                        "Unable to install solution. Please Check the Event Logs on all servers in the farm.", ee);
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "InstallSolution");
            }

            return true;
        }

        #endregion

        #region DeploySolution

        /// <summary>
        /// Deploys a solution .wsp file to sharepoint server
        /// </summary>
        /// <param name="solutionFile">the full path to the solution package file</param>
        /// <param name="solutionID">the SolutionId (GUID) of the solution</param>
        /// <returns>true if it was able to deploy</returns>
        public static bool DeploySolution(string solutionFile, string solutionID)
        {
            return DeploySolution(solutionFile, new Guid(solutionID), true, true);
        }

        public static bool DeploySolutionToContentApp(string solutionFile, Guid solutionID, string contentAppName)
        {
            try
            {
                TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "DeploySolutionToContentApp");

                SPSolution solution = LocateInFarm(solutionID);
                if (solution != null && solution.JobExists)
                {
                    StopSolutionJobs(solution);
                }

                // does not exist so add it
                if (solution == null)
                {
                    ValidationHelper.VerifyStringArgument(solutionFile, "SolutionFile");

                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Adding '" + solutionFile + "' to the Farm Solution Store.");
                    solution = SPFarm.Local.Solutions.Add(solutionFile);
                }

                // check to see if this has web application stuff
                if (solution.ContainsWebApplicationResource)
                {
                    Collection<SPWebApplication> webApplications = new Collection<SPWebApplication>();

                    // add request web applications
                    AddContentWebApplication(webApplications, contentAppName);

                    if (webApplications.Count > 0) 
                    {
                        // try to make sure we have at least one
                        // deploy it
                        TraceHelper.TraceVerbose(
                                TraceSwitch,
                                "SolutionHelper",
                                "WSP package deploying to " + webApplications.Count + " webapplication(s).");

                        solution.Deploy(Immediately, true, webApplications, true);
                    }
                    else
                    {
                        TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Could not deploy Solution as '" + contentAppName + "' is not found.");
                    }
                }
                else
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "WSP package deploying global as solution contains no Web Application Resources.");

                    // deploy it without web app stuff
                    solution.Deploy(Immediately, true, true);
                }

                WaitForJobToFinish(solution);
            }
            catch (NullReferenceException)
            {
                return false;
            }
            catch (Exception ee)
            {
                throw new Exception("Unable to deploy solution", ee);
            }
            finally 
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "DeploySolutionToContentApp");
            }

            return true;
        }

        /// <summary>
        /// Deploys the solution to administration app.
        /// </summary>
        /// <param name="solutionFile">The solution file.</param>
        /// <param name="solutionID">The solution ID.</param>
        /// <param name="adminAppName">Name of the admin app.</param>
        /// <returns></returns>
        public static bool DeploySolutionToAdministrationApp(string solutionFile, Guid solutionID, string adminAppName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "DeploySolutionToAdministrationApp");
            ValidationHelper.VerifyStringArgument(solutionFile, "solutionFile");
            ValidationHelper.VerifyStringArgument(adminAppName, "adminAppName");

            try
            {
                SPSolution solution = LocateInFarm(solutionID);
                if (solution != null && solution.JobExists)
                {
                    StopSolutionJobs(solution);
                }

                // does not exist so add it
                if (solution == null)
                {
                    ValidationHelper.VerifyStringArgument(solutionFile, "SolutionFile");
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Addiing '" + solutionFile + "' to the Farm Solution Store.");
                    solution = SPFarm.Local.Solutions.Add(solutionFile);
                }

                // check to see if this has web application stuff
                if (solution.ContainsWebApplicationResource)
                {
                    Collection<SPWebApplication> webApplications = new Collection<SPWebApplication>();

                    // add request web applications
                    AddWebApplication(webApplications, adminAppName);

                    if (webApplications.Count > 0) 
                    {
                        // deploy it
                        // try to make sure we have at least one
                        TraceHelper.TraceVerbose(
                                TraceSwitch,
                                "SolutionHelper",
                                "WSP package deploying to " + webApplications.Count + " webapplication(s).");

                        solution.Deploy(Immediately, true, webApplications, true);
                    }
                    else
                    {
                        TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Could not deploy Solution as '" + adminAppName + "' is not found.");
                    }
                }
                else
                {
                    TraceHelper.TraceVerbose(
                            TraceSwitch,
                            "SolutionHelper",
                            "WSP package deploying global as solution contains no Web Application Resources.");

                    // deploy it without web app stuff
                    solution.Deploy(Immediately, true, true);
                }

                WaitForJobToFinish(solution);
            }
            catch (NullReferenceException)
            {
                return false;
            }
            catch (Exception ee)
            {
                throw new Exception("Unable to deploy solution", ee);
            }
            finally 
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "DeploySolutionToAdministrationApp");
            }

            return true;
        }

        /// <summary>
        /// Deploys a solution .wsp file to sharepoint server
        /// </summary>
        /// <param name="solutionFile">the full path to the solution package file</param>
        /// <param name="solutionID">the SolutionId (GUID) of the solution</param>
        /// <param name="deployToAllWebApplications">true if you want to register package with all of the WebApplications on sharepoint server</param>
        /// <param name="deployToAllContentWebApplications">true if you want to register package with all of the content WebApplications on the sharepoint server</param>
        /// <returns>true if it was able to deploy</returns>
        public static bool DeploySolution(
                string solutionFile,
                Guid solutionID,
                bool deployToAllWebApplications,
                bool deployToAllContentWebApplications)
        {
            try
            {
                TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "DeploySolution");

                SPSolution solution = LocateInFarm(solutionID);
                if (solution != null && solution.JobExists)
                {
                    StopSolutionJobs(solution);
                }

                // does not exist so add it
                if (solution == null)
                {
                    ValidationHelper.VerifyStringArgument(solutionFile, "SolutionFile");
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Addiing '" + solutionFile + "' to the Farm Solution Store.");
                    solution = SPFarm.Local.Solutions.Add(solutionFile);
                }

                // check to see if this has web application stuff
                if (solution.ContainsWebApplicationResource)
                {
                    Collection<SPWebApplication> webApplications = new Collection<SPWebApplication>();

                    // add request web applications
                    if (deployToAllWebApplications) AddAllWebApplications(webApplications);
                    if (deployToAllContentWebApplications) AddAllContentWebApplications(webApplications);

                    if (webApplications.Count == 0)
                    {
                        // try to make sure we have at least one
                        SPWebApplication app =
                                SPWebService.AdministrationService.WebApplications.GetEnumerator().Current;
                        if (app == null) app = SPWebService.ContentService.WebApplications.GetEnumerator().Current;
                    }

                    // deploy it
                    solution.Deploy(Immediately, true, webApplications, true);
                }
                else
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "WSP package deploying global.");
                    
                    // deploy it without web app stuff
                    solution.Deploy(Immediately, true, true);
                }

                WaitForJobToFinish(solution);
            }
            catch (NullReferenceException exception)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "DeploySolution", exception);
                return false;
            }
            catch (Exception ee)
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "DeploySolution");
                throw new Exception("Unable to deploy solution", ee);
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "DeploySolution");
            }

            return true;
        }

        #endregion DeploySolution

        #region Uninstall solution

        /// <summary>
        /// Uninstalls the solution.
        /// </summary>
        /// <param name="solutionFile">The solution file.</param>
        /// <param name="solutionID">The solution ID.</param>
        /// <returns></returns>
        public static bool UninstallSolution(string solutionFile, string solutionID)
        {
            return UninstallSolution(solutionFile, new Guid(solutionID));
        }

        /// <summary>
        /// Uninstalls the solution.
        /// </summary>
        /// <param name="solutionFile">The solution file.</param>
        /// <param name="solutionID">The solution ID.</param>
        /// <returns></returns>
        public static bool UninstallSolution(string solutionFile, Guid solutionID)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "UninstallSolution");
            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "SolutionHelper",
                    "Uninstalling the solution (FilePath='" + solutionFile + "' | solutionID='" + solutionID + "')");

            SPSolution solution;
            try
            {
                solution = LocateInFarm(solutionID);

                if (solution == null)
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution NOT found");
                    
                    return true;
                }

                string solutionName = solution.Name;
                TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution to uninstall : '" + solutionName + "'");

                if (RetractSolution(solutionFile, solutionID))
                {
                    // Wait for it to end.
                    WaitForJobToFinish(solution);

                    SPFarm.Local.Solutions.Remove(solutionID);
                }
            }
            catch (NullReferenceException nre)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "UninstallSolution", nre);
                return false;
            }
            catch (Exception ee)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "UninstallSolution", ee);
                throw;
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "UninstallSolution");
            }

            return true;
        }

        #endregion

        #region UpgradeSolution

        /// <summary>
        /// Upgrades a solution using a .wsp file to sharepoint server
        /// </summary>
        /// <param name="solutionFile">the full path to the solution package file</param>
        /// <param name="solutionID">the SolutionId (GUID) of the solution</param>
        /// <returns>true if it was able to upgrade the solution</returns>
        public static bool UpgradeSolution(string solutionFile, string solutionID)
        {
            return UpgradeSolution(solutionFile, new Guid(solutionID));
        }

        /// <summary>
        /// Upgrades a solution using a .wsp file to sharepoint server
        /// </summary>
        /// <param name="solutionFile">the full path to the solution package file</param>
        /// <param name="solutionID">the SolutionId (GUID) of the solution</param>
        /// <returns>true if it was able to upgrade the solution</returns>
        public static bool UpgradeSolution(string solutionFile, Guid solutionID)
        {
            try
            {
                TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "UpgradeSolution");

                if (string.IsNullOrEmpty(solutionFile))
                {
                    throw new Exception("No solution file specified.");
                }

                if (!File.Exists(solutionFile))
                {
                    throw new Exception("Solution file not found.");
                }

                SPSolution solution = SPFarm.Local.Solutions[solutionID];
                StopSolutionJobs(solution);

                solution.Upgrade(solutionFile, Immediately);
            }
            catch (NullReferenceException exception)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "UpgradeSolution", exception);
                return false;
            }
            catch (InvalidOperationException exception)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "UpgradeSolution", exception);
                return DeploySolution(solutionFile, solutionID.ToString());
            }
            catch (Exception eee)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "UpgradeSolution", eee);
                throw new Exception("Unable to upgrade solution.", eee);
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "UpgradeSolution");
            }

            return true;
        }

        #endregion UpgradeSolution

        #region RetractSolution

        /// <summary>
        /// Retracts a solution using a .wsp file from a sharepoint server
        /// </summary>
        /// <param name="solutionFile">the full path to the solution package file</param>
        /// <param name="solutionID">the SolutionId (GUID) of the solution</param>
        /// <returns>true if it was able to retract the solution</returns>
        public static bool RetractSolution(string solutionFile, Guid solutionID)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "RetractSolution");
            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "SolutionHelper",
                    "Retracting Solution (solutionFile='" + solutionFile + "' | solutionID='" + solutionID + "')");

            try
            {
                SPSolution solution = LocateInFarm(solutionID);
                if (solution == null)
                    throw new Exception("Solution currently not deployed to server.  Cannot retract.");

                StopSolutionJobs(solution);

                if (solution.Deployed)
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution is deployed");
                    if (solution.ContainsWebApplicationResource)
                    {
                        Collection<SPWebApplication> deployedWebApplications = new Collection<SPWebApplication>();
                        AddAllConfiguredWebApplications(solution, deployedWebApplications);
                        solution.Retract(Immediately, deployedWebApplications);
                    }
                    else
                    {
                        TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Retracting '" + solution.Name + "' now");
                        solution.Retract(Immediately);
                    }

                    // Wait for the retract job to finish
                    WaitForJobToFinish(solution);
                }
                else
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Solution is NOT deployed");
                }
            }
            catch (NullReferenceException nref)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "RetractSolution", nref);
                return false;
            }
            catch (Exception ee)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "RetractSolution", ee);
                throw new Exception(
                        "Unable to retract solution. Please Check the Event Logs on all servers in the farm.", ee);
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "RetractSolution");
            }

            return true;
        }

        #endregion RetractSolution

        /// <summary>
        /// Waits for job to finish.
        /// </summary>
        /// <param name="solution">The solution.</param>
        /// <param name="maxMinutesToWait">The max minutes to wait.</param>
        private static void WaitForJobToFinish(SPSolution solution, int maxMinutesToWait)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "SolutionHelper", "WaitForJobToFinish");

            if (solution == null) return;

            DateTime stopTime = DateTime.Now.AddMinutes(maxMinutesToWait);
            TraceHelper.TraceVerbose(TraceSwitch, "SolutionHelper", "Waiting for the Deployment Job to finish");

            try
            {
                while (solution.JobExists)
                        //if (solution.JobStatus == SPRunningJobStatus.Initialized
                        //|| solution.JobStatus == SPRunningJobStatus.Scheduled)
                {
                    TraceHelper.TraceVerbose(
                            TraceSwitch,
                            "SolutionHelper",
                            "Waiting 500 milliseconds for solution ('" + solution.Name + "') timer job to finish.");

                    Thread.Sleep(500);
                    if (DateTime.Now > stopTime)
                    {
                        TraceHelper.TraceVerbose(
                                TraceSwitch,
                                "SolutionHelper",
                                "Timed out waiting for a WSP job to complete. Please check that " +
                                "all servers in your farm are all operational, to prevent");

                        throw new TimeoutException(
                                "Timed out waiting for Deployment job to complete on this solution : " + solution.Name);
                    }
                }
            }
            catch (Exception ex)
            {
                TraceHelper.TraceException(TraceSwitch, "SolutionHelper", "WaitForJobToFinish", ex);
                throw new Exception("Error while waiting to finish running jobs.", ex);
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "SolutionHelper", "WaitForJobToFinish");
            }
        }

        /// <summary>
        /// If a job is running on the solution, this method waits it to finish.
        /// </summary>
        /// <param name="solution"></param>
        private static void WaitForJobToFinish(SPSolution solution)
        {
            WaitForJobToFinish(solution, 10);
        }
    }
}